/**
 * 
 */
package cz.geoserver.KMLpublisher.util;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import cz.geoserver.KMLpublisher.db.model.GeometryFeature;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * Class for compiling KML responses and sending them back to client by Stream or Writer
 * @author mkepka
 *
 */
public class KMLwriter {

    private static String tempPlacemarkName = "placemarkExtData.ftl";
    private static String tempPlacemarkFullName = "cz/geoserver/KMLpublisher/util/placemarkExtData.ftl";
    private static String tempNetLinkName = "networklinkKML.ftl";
    private static String tempNetLinkFullName = "cz/geoserver/KMLpublisher/util/networklinkKML.ftl";
    private static Template placemark = null;
    private static Template netLink = null;
    private static String sqlIdParName = "sqlId";
    private static String NL = System.getProperty("line.separator");

    /**
     * Constructor creates instance of class and loads template of KML from files. 
     * @param fromServlet - Boolean is true if app runs as web-app and class is called from servlet, 
     * false if app runs as Geoserver plugin 
     */
    public KMLwriter(boolean fromServlet){
        try {
            Configuration cfg = new Configuration();
            if(fromServlet){
                //setting templates from stand alone files
                String resourcePathName = getClass().getResource(tempNetLinkName).getFile();
                String resourceDirName = resourcePathName.replace(tempNetLinkName, "");
                File templateDir = new File(resourceDirName);
                cfg.setDirectoryForTemplateLoading(templateDir);
                cfg.setObjectWrapper(new DefaultObjectWrapper());
                placemark = cfg.getTemplate(tempPlacemarkName);
                netLink = cfg.getTemplate(tempNetLinkName);
            }
            else{
                // setting templates from jar file
                placemark = new Template("placemark", new StringReader(loadTemplateFromJar(tempPlacemarkFullName)), cfg);
                netLink = new Template("netLink", new StringReader(loadTemplateFromJar(tempNetLinkFullName)), cfg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Method compiles KML with Networklink and sends it to client.
     * This method is used if app runs as Geoserver plugin. Whole response is completed in one String and then sent to client as Stream.
     * @param out - OutputStream where will be response sent.
     * @param sqlId - An identifier that will be used to load SQL query from Map when a request on data will come. 
     * @param requestURL - Part of the URL where to send requests.
     * @param sql - Instance of SQLparser that stores incoming SQL query 
     */
    public void sendKMLNetworklinkAsStream(OutputStream out, String sqlId, String requestURL, SQLparser sql) {
        try {
        // Filling up KML template by values
        // KML Element <Document>
            Map<String, String> mapDoc = new HashMap<String, String>();
            mapDoc.put("name", "KML with Network Link");
            mapDoc.put("visibility", "1");
            mapDoc.put("open","1");
            mapDoc.put("description","Geometry features are loaded as results of SQL query bounded by current extent of window.");
        // KML Element <Networklink>
            Map<String, String> mapNL = new HashMap<String, String>();
            mapNL.put("name","Geometry Features");
            mapNL.put("visibility","1");
            mapNL.put("open","1");
            mapNL.put("description","Features loaded from database by following SQL: "+sql.getSqlOriginalString());
            mapNL.put("refreshVisibility","0");
            mapNL.put("flyToView","0");
            String href = requestURL+sqlIdParName+"="+sqlId;
            mapNL.put("href",href);
            mapNL.put("refreshInterval","2");
            mapNL.put("viewRefreshMode","onStop");
            mapNL.put("viewRefreshTime","1");
        // KML Element <KML>
            Map<String, Map<String,String>> mapRoot = new HashMap<String, Map<String,String>>();
            mapRoot.put("document", mapDoc);
            mapRoot.put("networklink", mapNL);
        // Filling up KML template with values in Map 
            Writer outStr = new StringWriter();
            netLink.process(mapRoot, outStr);
         // Sending filled up KML to client
            out.write(outStr.toString().getBytes());
            //out.println(outStr.toString());
            out.flush();
            out.close();
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Method compiles KML with Networklink and sends it to client.
     * This method is used if app runs as servlet. Response is written out into output continuously.
     * @param pw - PrintWriter where will be response sent.
     * @param sqlId - An identifier that will be used to load SQL query from Map when a request on data will come.
     * @param requestURL - Part of the URL where to send requests.
     * @param sql - Instance of SQLparser that stores incoming SQL query
     */
    public void sendKMLNetworklink(PrintWriter pw, String sqlId, String requestURL, SQLparser sql) {
        try {
            // Filling up KML template by values
            // KML Element <Document>
            Map<String, String> mapDoc = new HashMap<String, String>();
            mapDoc.put("name", "KML with Network Link");
            mapDoc.put("visibility", "1");
            mapDoc.put("open","1");
            mapDoc.put("description","Features are results of this SQL: "+ sql.getSqlOriginalString());
            // KML Element <Networklink>
            Map<String, String> mapNL = new HashMap<String, String>();
            mapNL.put("name","Geometry Features");
            mapNL.put("visibility","1");
            mapNL.put("open","1");
            mapNL.put("description","Feature from database specified by SQL query");
            mapNL.put("refreshVisibility","0");
            mapNL.put("flyToView","0");
            String href = requestURL+sqlIdParName+"="+sqlId;
            mapNL.put("href",href);
            mapNL.put("refreshInterval","2");
            mapNL.put("viewRefreshMode","onStop");
                mapNL.put("viewRefreshTime","1");
            // KML Element <KML>
            Map<String, Map<String,String>> mapRoot = new HashMap<String, Map<String,String>>();
            mapRoot.put("document", mapDoc);
            mapRoot.put("networklink", mapNL);
            // Sending filled up KML template to client 
            netLink.process(mapRoot, pw);
            pw.close();
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Method compiles static KML with several Placemarks according to result of SQL query and sends it back to client.
     * This method is used if app runs as Geoserver plugin. Whole response is completed in one String and then sent to client as Stream.
     * @param out - ServletOutputStream from HttpServletResponse 
     * @param gfList List of GeometryFeatures that will be sent to client
     */
    public void sendStaticKMLAsStream(OutputStream out, List<GeometryFeature> gfList){
        try{
            // Assembling of KML response in StringWriter
            Writer outStr = new StringWriter();
            outStr.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document>\n");
            for(int i = 0; i<gfList.size();i++){
                GeometryFeature gf = gfList.get(i);
                Map<String, String> map = new HashMap<String, String>();
                map.put("placemarkID", String.valueOf(i));
                map.put("placemarkName", "Geometry Feature");
                map.put("placemarkExtData", gf.getKMLExtendedData());
                map.put("iconColor", "ff85befd");
                map.put("placemarkGeometry", gf.getGeomKML());
                placemark.process(map, outStr);
            }
            outStr.write("</Document>\n</kml>");
            // Sending KML response to client
            out.write(outStr.toString().getBytes());
            //out.println(outStr.toString());
            out.flush();
            out.close();
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Method compiles static KML with several Placemarks according to result of SQL query and sends it back to client.
     * This method is used if app runs as servlet. Response is written out into output continuously.
     * @param pw - PrintWriter from HttpServletResponse
     * @param gfList - List of Geometry features that will be sent to client
     */
    public void sendStaticKML(PrintWriter pw, List<GeometryFeature> gfList){
        try {
            //Sending part of KML response to client
            pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document>");
            for(int i = 0; i<gfList.size();i++){
                GeometryFeature gf = gfList.get(i);
                Map<String, String> map = new HashMap<String, String>();
                map.put("placemarkID", String.valueOf(i));
                map.put("placemarkName", "Geometry Feature");
                map.put("placemarkExtData", gf.getKMLExtendedData());
                map.put("iconColor", "ff85befd");
                map.put("placemarkGeometry", gf.getGeomKML());
                placemark.process(map, pw);
            }
            pw.println("</Document>\n</kml>");
            pw.close();
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Method reads template file from jar file into String
     * @param templateFileName - Full name of template file in package hierarchy 
     * @return String - Content of file as String
     * @throws IOException - Throws the IOException if an exception appears while reading of file.
     */
    private String loadTemplateFromJar(String templateFileName) throws IOException{
        URL url = KMLwriter.class.getClassLoader().getResource(templateFileName);
        Scanner sc = new Scanner(url.openStream(), "UTF-8");
        StringBuilder textBuild = new StringBuilder();
        while(sc.hasNextLine()){
            textBuild.append(sc.nextLine()+NL);
        }
        sc.close();
        return textBuild.toString();
    }
}